﻿/********************************************************************************
 * Developed by Phil Tyler (http://www.laidbackcoder.co.uk)
 * 
 * Cryptosolic is Licensed under the GNU Lesser General Public License (LGPL)
 * http://cryptosolic.codeplex.com/license
 * *****************************************************************************/
using System;
using System.ComponentModel;
using Cryptosolic.Security.Encryption.Exceptions;
using Cryptosolic.Security.Hashing;
using Cryptosolic.Utilities;

namespace Cryptosolic.Security.Encryption
{
	/// <summary>
    /// Encryption Algorithm Base Class
    /// </summary>
    /// <remarks>
    /// Used only to provide Base Methods to Sub Classes - DO NOT instantiate directly
    /// </remarks>
    [Browsable(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public abstract class EncryptionAlgorithm
	{
		private int hashIterations = 1;
		
		#region Properties
			
	        /// <summary>
	        /// Hashing Algorithm used to Generate Key
	        /// </summary>
	       	public abstract IHashingAlgorithm HashingAlg { get; }
	
        	/// <summary>
	        /// Key Size of Cipher (in bits)
	        /// </summary>
            public abstract int KeySize { get; }
    	
            /// <summary>
	        /// Bloc Size of Cipher (in bits)
	        /// </summary>
	        public abstract int BlockSize { get; }
        
	        /// <summary>
            /// Method to Pad Cipher Text
            /// </summary>
            public abstract BytePaddingMode Padding { get; }
	        
            /// <summary>
            /// Method used to Undertake Encryption/Decryption
            /// </summary>
            public abstract BlockCipherMode Mode { get; }
            
            /// <summary>
            /// Number of Iterations when Hashing the Key
            /// </summary>
            public int HashIterations
            {
                get { return hashIterations;  }
                set { hashIterations = value; }
            }
                 
        #endregion
        
		#region Helper Methods

            /// <summary>
			/// Generate a Random Initialisation Vector
			/// </summary>
			/// <returns>Random Bytes of relative size to the Cipher's Block Size</returns>      
			public byte[] GenerateRandomIV()
			{
				return RandomNumberGenerator.GenerateRandomData(BlockSize / 8);
			}
	    	
			/// <summary>
			/// Generate a Random Key
			/// </summary>
			/// <returns>Random Key of relative size to the Cipher's Key Size</returns>      
			public string GenerateRandomKey()
			{
				return DataConverter.BytesToString(RandomNumberGenerator.GenerateRandomData(KeySize / 8));
			}
			
			/// <summary>
			/// Generate a Random Salt
			/// </summary>
			/// <returns>Random Salt of relative size to the Cipher's Key Size</returns>      
			public string GenerateRandomSalt()
			{
				return GenerateRandomKey();
			}
	        
	        /// <summary>
        	/// Generate the Crypto Key
        	/// </summary>
        	/// <param name="key">Encryption/Decryption Key</param>
        	/// <param name="salt">Salt</param>
        	/// <returns>Encryption Key ad Byte Array</returns>
        	internal byte[] ComputeKey(string key, string salt)
        	{
        		int KeySizeInBytes = KeySize / 8;
        		
        		if(!string.IsNullOrEmpty(key))
	        	{
		        	if(HashingAlg != null)
		        	{
		        		if(salt == null)
		        		{
		        			salt = string.Empty;		        			
		        		}
		        		
		        		byte[] hashedKey = HashingAlg.HashData(key + salt, HashIterations).ToBytes();
		        		
		        		if(hashedKey.Length >= KeySizeInBytes)
		        		{
		        			byte[] keyOutput = new byte[KeySizeInBytes];
		        			
		        			for(int i= 0; i < keyOutput.Length; i++)
		        			{
		        				keyOutput[i] = hashedKey[i];
		        			}
		        			
		        			return keyOutput;
		        		}
		        		else
		        		{
		        			throw new InsufficientHashOutputSizeException("Hash Output Too Short");
		        		}
		        	}
		        	else
		        	{
		        		byte[] unhashedKey = DataConverter.StringToBytes(key);
		        		
		        		if(unhashedKey.Length >= KeySizeInBytes)
		        		{
		        			byte[] keyOutput = new byte[KeySizeInBytes];
		        			
		        			for(int i= 0; i < keyOutput.Length; i++)
		        			{
		        				keyOutput[i] = unhashedKey[i];
		        			}
		        			
		        			return keyOutput;
		        		}
		        		else
		        		{
		        			throw new InsufficientKeySizeException("Encryption Key Too Short");
		        		}
		        	}
	        	}
	        	else
	        	{
	        		throw new NullKeyException("Encryption Key cannot be Null or Blank");
	        	}
        	}
        
        #endregion
	}
}
